[WIP] Transforms [rebase of timelyportfolio's work]#936
Conversation
…ster `filter` with plotly
|
PS. never mind, they pass after registering the transforms, I guess these should eventually be done centrally right under Currently, tests pass because some tests are commented out for now. @timelyportfolio @etpinard did the commented out tests use to pass? (I mean in the feature branch rather than |
|
We left |
|
@timelyportfolio thanks, exactly, probably sticking it in the big list unless @etpinard you think otherwise (there may have been a reason to not include it in the feature branch) |
|
@etpinard (not transform related question) I just noticed that if I supply a scalar e.g. (Current |
|
@monfera don't worry about the current groubpy transform in That file was only meant for testing purposes. |
| valType: 'any', | ||
| dflt: 0 | ||
| }, | ||
| filtersrc: { |
There was a problem hiding this comment.
Can we add ids (edit: not identifiers) to the filtersrc enum? The previous blocker was the animation PR getting merged, but now that it's merged, it's probably a good idea. Without that, I believe @timelyportfolio had to use floating point equaity in order to use the lasso selector and then filter to the selected set. Since in already uses indexOf, I think this perhaps really is a one-liner.
There was a problem hiding this comment.
Thank you, yes. I was looking at the wrong branch. ids.
| return v; | ||
| }); | ||
| } else { | ||
| if(isNumeric(transformOut.value)) transformOut.value = +transformOut.value; |
There was a problem hiding this comment.
I would prefer moving the array item type conversion down to the transform step - supplyDefaults (even the transform version) should try to avoid diving into arrays.
There was a problem hiding this comment.
FYI I haven't touched anything in filter.js so it's now as it is in @timelyportfolio's branch (and I haven't yet pushed the changes to groupby.js. If I understand the task correctly, I need to work on groupby as part of the first sprint on it so I'll get back on it in a bit.
(cherry picked from commit 7e967fe)
…f more cases inside
| // list of underscore attributes to keep in schema as is | ||
| crawler.UNDERSCORE_ATTRS = [crawler.IS_SUBPLOT_OBJ, crawler.IS_LINKED_TO_ARRAY, crawler.DEPRECATED]; | ||
|
|
||
| crawler.crawl = function(attrs, callback, specifiedLevel) { |
There was a problem hiding this comment.
same here, I'd put this in coerce.js (if ok)
There was a problem hiding this comment.
@etpinard same question here, how would it best fit, as a separate export or ... pls specify?
There was a problem hiding this comment.
Similarly here:
export.crawl in coerce.js
|
|
||
| PlotSchema.crawl = function(attrs, callback) { | ||
| PlotSchema.crawl = function (attrs, callback, specifiedLevel) { | ||
| var level = specifiedLevel || 0; |
There was a problem hiding this comment.
Is it possible to add a docstring on the usage? This feels very useful, but I don't immediately comprehend what it does.
There was a problem hiding this comment.
Or maybe this isn't the method that needs the docstring, but from what I understand, these methods are critical for organizing the messy parts of working with the plot schema, so would be great to make preferred logic clear!
There was a problem hiding this comment.
As I had to get familiar with it, I can add docs. Caveat, the nearly identical crawler was already in place (in plot_schema.js) and I'm not the biggest Plotly schema expert.
There was a problem hiding this comment.
Thanks. Sorry for the extra burden. Not much necessary at all. Just a couple signposts to direct traffic. I just feel like we've been facing very similar challenges with these things, and I suspect a bug that I need to fix involves some of the same problems you've addressed here.
There was a problem hiding this comment.
Definitely, at least any gaps in my understanding can be cleared :-) Sometimes I'm thinking of adding verbiage but end up being more concerned with the large lifecycle functions, some of which are a few hundred lines.
There was a problem hiding this comment.
True, the crawler is already accessible publicly on Plotly.PlotSchema.crawl though it isn't an official plotly.js method yet.
Maybe we should add it the main Plotly object in v2.0.0 e.g. Plotly.crawlAttributes ?
| } | ||
| } | ||
|
|
||
| crawler.crawl(trace._module.attributes, callback); |
There was a problem hiding this comment.
Is the crawler able to catch nested attributes like error_y.array declared here ?
If not, it's not a big deal. I'm not a big fan of the _nestedAttributes pattern anyway. We could simple require nested attribute in the root attribute modules to make this work.
There was a problem hiding this comment.
I don't know offhand, if it's not a big deal either way, I'll take a quick look and either cover it or reify it with a test case.
| return newData; | ||
| } | ||
|
|
||
| function getDeepProp(thing, propArray) { |
There was a problem hiding this comment.
getDeepProp and setDeepProp would be nice additions to src/lib/nested_property.js.
There was a problem hiding this comment.
I'll take a look! Btw. I was also thinking about using deepExtendas an alternative to the attrib path arrays and setDeepProp but I had performance concerns, it's already in a deeply nested loop so we might want to iterate on performance at some point.
There was a problem hiding this comment.
I'd vote for using setDeepProp - though I haven't ran any benchmarking.
There was a problem hiding this comment.
Turns out that nested_property.js yields one single export, which is a function, do you want that I refactor its single usage (by coerce.js) such that nested_property.js can export multiple things?
| expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
| expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
|
||
| expect(gd._fullData.length).toEqual(0); // fixme: it passes with 0; shouldn't it be 1? (one implied group) |
There was a problem hiding this comment.
Good point here.
What should a groupby transform with no groups field do?
In that case maybe it would be less surprising to have groupby not do anything (i.e. the identity transform).
There was a problem hiding this comment.
I think it should also do the same as when transforms.groups.length === 0. Which leads to the question, what if transforms.groups.length < x.length and also, what we do now when arrays are of non-shared length.
What the code currently does is, it iterates through the elements of groups on 0 -> length - 1 i.e. the result will be empty.
While I can conditionalize it to do something differently (e.g. if(transforms.groups === void(0) || transforms.groups.length === 0) return; or some such) the user may pass e.g. one element in groups and gets another behavior.
@etpinard what's the desired outcome if
- the user doesn't specify a
transforms.groups - the user does specify a
transforms.groupsbut it'snull,[],undefined - the user specifies a
transforms.groupsarray but its length is shorter than the length of the (shortest?) data array
I'm almost leaning toward leaving the code as it is (i.e. no added logic), in favor of letting others work with groupby as early as possible, but it's probably because I'm not yet familiar with strong conventions in this realm so I'll gladly do what's deemed best.
There was a problem hiding this comment.
- the user doesn't specify a transforms.groups
skip over the transform
- the user does specify a transforms.groups but it's null, [], undefined
same here, skip over the transform
- the user specifies a transforms.groups array but its length is shorter than the length of the (shortest?) data array
The resulting extended should as many points as items in groups. In other words, skip over trace items that can't be paired up with a groups item
| }); | ||
|
|
||
| // passes; looks OK | ||
| it('Plotly.plot should plot the transform traces', function(done) { |
| expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
| expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
|
||
| expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK |
There was a problem hiding this comment.
This result is ok.
Non-array transforms values should be ignored.
| expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
| expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
|
||
| expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK |
There was a problem hiding this comment.
This is the expected result.
| } | ||
| }, | ||
| b: { | ||
| mode: 'markers+lines', // heterogeonos attributes are OK: group 'a' doesn't need to define this |
| line: { | ||
| width: [4, 2, 4, 2, 2, 3, 3], | ||
| // a general, not overridden array will be interpreted per group | ||
| color: ['orange', 'red', 'green', 'cyan'] |
There was a problem hiding this comment.
so what's the output here?
There was a problem hiding this comment.
If I understand correctly, transforms[0].style[a_or_b].maker.color overrides the trace marker.color ?
There was a problem hiding this comment.
Trace belonging to group a gets the marker.line.color from the split array: ['orange', 'red', 'cyan', 'pink']
Trace belonging to group b gets the marker.line.color from transforms.style.b.marker.line.color: yellow
I updated the test case to demonstrate it (will push soon).
| exports.UNDERSCORE_ATTRS = [exports.IS_SUBPLOT_OBJ, exports.IS_LINKED_TO_ARRAY, exports.DEPRECATED]; | ||
|
|
||
| /** | ||
| * Crawl the attribute tree, recursively calling a callback function |
|
|
||
| var opts = state.transform; | ||
| var groups = opts.groups; | ||
| var groups = trace.transforms[state.transformIndex].groups; |
There was a problem hiding this comment.
what's the difference here between
state.transform
// and
trace.transforms[state.transformIndex]??
There was a problem hiding this comment.
@etpinard the latter is the pre-transform information. We want to obtain the groups. If a filter applies before a groupby then state.transform.groups will be an array that's already culled by the filter.
| var arrayAttributes = attributeSet | ||
| .filter(function(array) {return Array.isArray(Lib.nestedProperty(trace, array).get());}); | ||
|
|
||
| // fixme the O(n**3) complexity |
There was a problem hiding this comment.
True, we could probably do better in terms of performance, but O(n^3) where n is the number of attributes doesn't bother me too much,
There was a problem hiding this comment.
I'll remove the comment and iterate on performance either separately or as needed (as you see fit)
There was a problem hiding this comment.
Remove the comment for now. Like I said big loops over attributes don't bother me (as opposed to big loops over data arrays).
| expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
| expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
|
||
| expect(gd._fullData.length).toEqual(1); // fixme not: good, okay it's 1 here (one implied group / thing) |
| * Example of filled closure variable (expected to be initialized to []): | ||
| * [["marker","size"],["marker","line","width"],["marker","line","color"]] | ||
| */ | ||
| function callback(attr, attrName, attrs, level) { |
There was a problem hiding this comment.
Interesting! Thanks for the docs. This might be applicable in a few situations.
Rebase and lint of @timelyportfolio's PR #859
Issue #917